package google.code.asoap.util;

import google.code.asoap.IMethodResponseParser;
import google.code.asoap.annotation.SOAPObject;
import google.code.asoap.annotation.SOAPProperty;
import google.code.asoap.annotation.SOAPProperty.Type;
import google.code.asoap.annotation.SOAPService;
import google.code.asoap.annotation.SOAPServiceOperation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

public class SoapAnnotationDataCache {

	private static final HashMap<Class<?>, ClassData> classDataCache = new HashMap<Class<?>, ClassData>();
	private static final HashMap<Method, ServiceInterfaceData> serviceInterfaceDataCache = new HashMap<Method, ServiceInterfaceData>();
	
	public static ClassData getClassData(Class<?> clazz) {
		ClassData classData = classDataCache.get(clazz);

		if (classData == null) {
			SOAPObject annotation = clazz.getAnnotation(SOAPObject.class);

			if (annotation == null) {
				throw new IllegalArgumentException("Class " + clazz.getSimpleName() + " doesn't have a SOAPObject annotation.");
			}

			classData = new ClassData(clazz, annotation);

			Field[] fields = clazz.getDeclaredFields();
			for (Field field : fields) {
				SOAPProperty propertyAnnotation = field.getAnnotation(SOAPProperty.class);
				if (propertyAnnotation != null) {
					field.setAccessible(true);

					FieldData fd = new FieldData(field, propertyAnnotation);
					classData.fields.add(fd);
				}
			}

			classDataCache.put(clazz, classData);
		}

		return classData;
	}
	
	public static ServiceInterfaceData addServiceInterface(Class<?> declaringClass) throws MalformedURLException {
		final SOAPService annotation = declaringClass.getAnnotation(SOAPService.class);
		
		if (annotation == null) {
			throw new IllegalArgumentException("Class " + declaringClass.getSimpleName() + " doesn't have a SOAPServiceInterface annotation.");
		}
		
		final ServiceInterfaceData serviceInterfaceData = new ServiceInterfaceData(annotation);
		
		final Method[] methods = declaringClass.getMethods();
		for (Method m : methods) {
			SOAPServiceOperation methodAnnotation = m.getAnnotation(SOAPServiceOperation.class);
			if (methodAnnotation != null) {					
				ServiceMethodData methodData = new ServiceMethodData(m, methodAnnotation);
				serviceInterfaceData.methods.put(m, methodData);
				serviceInterfaceDataCache.put(m, serviceInterfaceData);
			}
		}	
		
		return serviceInterfaceData;
	}
	
	public static ServiceInterfaceData getServiceInterfaceData(Method method) throws MalformedURLException
	{
		ServiceInterfaceData serviceInterfaceData = serviceInterfaceDataCache.get(method);
		if (serviceInterfaceData == null) {
			serviceInterfaceData = addServiceInterface(method.getDeclaringClass());
		}
		return serviceInterfaceData;
	}

	public static class ClassData {
		private Class<?> clazz;
		private String namespace;
		private String typeId;
		private List<FieldData> fields = new ArrayList<FieldData>();
		
		public ClassData(Class<?> clazz, SOAPObject annotation) {
			this.clazz = clazz;
			this.namespace = annotation.namespace();
			this.typeId = annotation.typeId();
		}

		public Class<?> getClazz() {
			return clazz;
		}

		public String getNamespace() {
			return namespace;
		}
		
		public String getTypeId() {
			return typeId;
		}

		public List<FieldData> getFields() {
			return fields;
		}
	}

	public static class FieldData {
		private final Field field;
		private final Type type;
		private String name;
		
		public FieldData(Field field, SOAPProperty propertyAnnotation) {
			this.field = field;
			this.name = propertyAnnotation.name();
			this.type = propertyAnnotation.type();
			if ("".equals(this.name))
				this.name = field.getName();
		}

		public Field getField() {
			return field;
		}
		
		public String getName() {
			return name;
		}
		
		public Type getType() {
			return type;
		}
	}
	
	public static class ServiceInterfaceData {
		private final String namespace;
		private final String serviceName;
		private final HashMap<Method, ServiceMethodData> methods = new HashMap<Method, ServiceMethodData>();
		private URL serverUrl;
		private String serviceInterface;
		
		public ServiceInterfaceData(SOAPService annotation) throws MalformedURLException {
			this.serverUrl = new URL(annotation.serverUrl());
			this.namespace = annotation.namespace();
			this.serviceName = annotation.serviceName();			
			this.serviceInterface = annotation.serviceInterface();
			if ("".equals(serviceInterface)) {
				serviceInterface = "I" + serviceName;
			}
		}

		public URL getServerUrl() {
			return serverUrl;
		}
		
		public String getNamespace() {
			return namespace;
		}
		
		public String getServiceName() {
			return serviceName;
		}
		
		public String getServiceInterface() {
			return serviceInterface;
		}
		
		public Collection<ServiceMethodData> getMethods()
		{
			return methods.values();
		}

		public ServiceMethodData getMethodData(Method method) {
			return methods.get(method);
		}

		public void setServerUrl(URL serverUrl) {
			this.serverUrl = serverUrl;
		}
	}
	
	public static class ServiceMethodData {
		private final IMethodResponseParser parser;
		private final String[] parameterNames;
		private String name;
		
		public ServiceMethodData(Method method, SOAPServiceOperation methodAnnotation) {
			try {
				this.parser = methodAnnotation.parser().newInstance();
			} catch (Exception e) {
				throw new IllegalArgumentException("MethodResponseParser " + methodAnnotation.parser() + " defines no public, no-args constructor. Could not instantiate.");
			}
			
			name = methodAnnotation.name();
			if ("".equals(name)) {
				name = method.getName();
			}
			
			this.parameterNames = methodAnnotation.parameterNames();
		}

		public String getName() {
			return name;
		}
		
		public IMethodResponseParser getParser() {
			return parser;
		}
		
		public String[] getParameterNames() {
			return this.parameterNames;
		}
	}
}
